﻿/*
COPYRIGHT (C) 2008 EPISERVER AB

THIS FILE IS PART OF WIKIX.
WIKIX IS FREE SOFTWARE: YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF THE GNU LESSER GENERAL PUBLIC LICENSE VERSION v2.1 AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION.

WIKIX IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU LESSER GENERAL PUBLIC LICENSE FOR MORE DETAILS.
YOU SHOULD HAVE RECEIVED A COPY OF THE GNU LESSER GENERAL PUBLIC LICENSE ALONG WITH WIKIX. IF NOT, SEE <HTTP://WWW.GNU.ORG/LICENSES/>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Security;
using System.Security;
using System.Web;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.Research.Wiki.Core;
using EPiServer.Web;
using EPiServer.Filters;
using System.Web.Routing;
using EPiServer.Security;
using EPiServer.Core.Html;
using System.Collections;
using EPiServer.Web.PageExtensions;
using System.Web.Profile;
using System.Security.Principal;
using System.Threading;
using System.Web.UI;
using System.Text;
using System.Globalization;

namespace EPiServer.Research.Wiki
{
    public class WikiManager
    {
        public const string WIKIROOTNAME = "[Wiki] Root";
        public const string WIKIARTICLENAME = "[Wiki] Article";
        public const string WIKISECTIONNAME = "[Wiki] Section";
        public const string WIKIDISCUSSIONNAME = "[Wiki] Discussion";
        public const int SECTIONDIST = 10000;
        private const int _previewTextLength = 150;

        protected static ReaderWriterLock pagenameslock = new ReaderWriterLock();


        public Dictionary<PageReference, Dictionary<string, PageReference>> WikiPageNames;

        public WikiParser Parser;

        #region CreateCriteria

        public PropertyCriteria CreateCriteria(CompareCondition condition, string name, PropertyDataType dataType, string value, bool required)
        {
            PropertyCriteria criteria = new PropertyCriteria();

            criteria.Condition = condition;
            criteria.Name = name;
            criteria.Type = dataType;
            criteria.Value = value;
            criteria.Required = required;

            return criteria;
        }

        #endregion

        #region Singleton
        private static WikiManager _instance;

        public static WikiManager Instance
        {
            get
            {
                if (_instance == null) _instance = new WikiManager();
                return _instance;
            }
        }
        #endregion

        #region GetWikiRoot


        public void InitRootWikis()
        {
            pagenameslock.AcquireReaderLock(Timeout.Infinite);
            if (null == WikiPageNames)
            {
                LockCookie lc = pagenameslock.UpgradeToWriterLock(Timeout.Infinite);

                if (null == WikiPageNames)
                {
                    WikiPageNames = new Dictionary<PageReference, Dictionary<string, PageReference>>();

                    if (PageType.Load(WIKIROOTNAME) != null)
                    {


                        //Find all wiki's
                        PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();
                        pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WIKIROOTNAME).ID.ToString(), true));

                        //TODO: Consider if start should be start page or root - and if root, how to avoid recyclebin
                        PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(PageReference.StartPage, pcc);
                        foreach (PageData pd in pdc)
                        {
                            //Build name Dictionary
                            AddWikiRoot(pd);
                        }

                    }
                }

                pagenameslock.DowngradeFromWriterLock(ref lc);
            }
            pagenameslock.ReleaseReaderLock();
        }

        public PageData GetWikiRoot(PageReference pr)
        {
            return GetWikiRoot(DataFactory.Instance.GetPage(pr));
        }

        public PageData GetWikiRoot(PageData pd)
        {
            if (pd.PageTypeName == WIKIROOTNAME) return pd;
            else if (pd.ParentLink == PageReference.RootPage) return null;
            else return GetWikiRoot(pd.ParentLink);
        }


        #endregion

        #region GetWikiArticle

        public PageData GetWikiArticle(PageReference pr)
        {
            return GetWikiArticle(DataFactory.Instance.GetPage(pr));
        }

        public PageData GetWikiArticle(PageData pd)
        {
            if ((pd.PageTypeName == WIKIARTICLENAME) || (pd.PageTypeName == WIKIROOTNAME)) return pd;
            else if (pd.ParentLink == PageReference.RootPage) return null;
            else return GetWikiArticle(pd.ParentLink);
        }

        #endregion

        #region Handle Page Names

        public void Initialize()
        {
            //Subscribe to publish / delete events to update list
            DataFactory.Instance.PublishedPage += new PageEventHandler(Instance_PublishedPage);
            DataFactory.Instance.MovingPage += new PageEventHandler(Instance_MovingPage);
        }

        void Instance_MovingPage(object sender, PageEventArgs e)
        {
            //TODO: What if moving page from one wiki to another?
            if (e.TargetLink == PageReference.WasteBasket)
            {
                //Deleted
                //handle deleted WikiRoot
                if (WikiPageNames.ContainsKey(e.PageLink))
                {
                    WikiPageNames.Remove(e.PageLink);
                    string WikiID = e.PageLink.ID.ToString();
                    RemoveRootRoutes(WikiID);
                    return;
                }

                //Deleted article
                PageData pd = DataFactory.Instance.GetPage(e.PageLink);
                if (pd.PageTypeName != WIKIARTICLENAME) return;
                PageData curWiki = GetWikiRoot(pd);
                if ((curWiki != null) && (WikiPageNames.ContainsKey(curWiki.PageLink)))
                {
                    WikiPageNames[curWiki.PageLink]
                        .Where(kv => kv.Value == e.PageLink)
                        .Select(kv => kv.Key)
                        .ToList()
                        .ForEach(s => WikiPageNames[curWiki.PageLink].Remove(s));

                }
            }
        }



        private void AddNamesToList(Dictionary<string, PageReference> nameList, PageData article)
        {
            if (!nameList.ContainsKey(article.PageName.ToLower()))
            {
                nameList.Add(article.PageName.ToLower(), article.PageLink);
            }
            if (!nameList.ContainsKey(article["PageURLSegment"].ToString().ToLower()))
            {
                nameList.Add(article["PageURLSegment"].ToString().ToLower(), article.PageLink);
            }
        }

        private void AddWikiRoot(PageData pd)
        {

            //Add to WikiPageNames
            if (WikiPageNames.ContainsKey(new PageReference(pd.PageLink.ID))) return; //Already exists
            //TODO: Fix usage of ID - while still ignore work-id's

            //Setup Routing
            AddWikiRoutes(pd);


            //Add wiki to main index
            WikiPageNames.Add(new PageReference(pd.PageLink.ID, true), new Dictionary<string, PageReference>());

            //Add all articles to lookup. Note that this is done in a seperate thread.
            System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(BuildNameList), (object)pd);
            //BuildNameList(pd); Exchange direct call with thread pool call.


        }

        private void BuildNameList(object o)
        {
            PageData pd = o as PageData;
            Dictionary<string, PageReference> nameList = WikiPageNames[new PageReference(pd.PageLink.ID, true)];



            foreach (PageData article in DataFactory.Instance.GetChildren(pd.PageLink))
            {
                if (article.PageTypeName == WIKIARTICLENAME)
                {
                    AddNamesToList(nameList, article);
                }
            }
            //Add the root article
            AddNamesToList(nameList, pd);
        }




        void Instance_PublishedPage(object sender, PageEventArgs e)
        {
            //handle Published WIKIROOT
            if (e.Page.PageTypeName == WIKIROOTNAME)
            {
                AddWikiRoot(e.Page);
            }
            //TODO: Handle Languages
            if (e.Page.PageTypeName != WIKIARTICLENAME) return;
            PageData curWiki = GetWikiRoot(e.Page);
            if ((curWiki != null) && (WikiPageNames.ContainsKey(curWiki.PageLink)))
            {
                var PageExist = WikiPageNames[curWiki.PageLink].Where(vp => e.PageLink.CompareToIgnoreWorkID(vp.Value)).Select(vp => vp.Key);
                //TODO: Remove all references, not just first
                if ((PageExist != null) && (PageExist.Count() == 1)) WikiPageNames[curWiki.PageLink].Remove(PageExist.First());
                AddNamesToList(WikiPageNames[curWiki.PageLink], e.Page);
            }




        }


        public PageReference FindWikiArticle(PageReference sender, string Name)
        {
            PageData curWiki = GetWikiRoot(sender);
            if (curWiki != null)
            {
                if (WikiPageNames.ContainsKey(curWiki.PageLink))
                {
                    if (WikiPageNames[curWiki.PageLink].ContainsKey(Name.ToLower()))
                    {
                        return WikiPageNames[curWiki.PageLink][Name.ToLower()];
                    }
                }
            }
            return PageReference.EmptyReference;
        }

        public List<string> FindWikiArticleArray(PageReference sender, string query)
        {
            List<string> list = new List<string>();

            PageData curWiki = GetWikiRoot(sender);
            if (curWiki != null)
            {
                if (WikiPageNames.ContainsKey(curWiki.PageLink))
                {
                    foreach (string s in WikiPageNames[curWiki.PageLink].Keys)
                    {
                        if (s.Contains(query))
                        {
                            list.Add(s);
                        }
                    }


                }
            }
            return list;
        }

        #endregion

        #region Routing

        private static void AddWikiRoutes(PageData pd)
        {
            //TODO: Check if routes already registrered
            string path = WikiManager.ConstructAppRelativePath(pd.PageLink).TrimStart('/');

            if (LanguageBranch.ListEnabled().Count > 1)
            {
                //multi-language
                path = "{lang}/" + path;
            }
            //NOTE: Reason ID is used on the Pagereferences below is to ignore specific versions
            //Category Route
            RouteTable.Routes.Add("CategoryRoute" + pd.PageLink.ID.ToString(),
                new Route(path + GetRoutemapFromXml("/wiki/urlroutes/category", "{Category1}", "{*Category2}"), new WikiRouteHandler(pd.PageLink, "~/EPiServer.Research.Wiki/Pages/CategoryList.aspx", AccessLevel.Read)));
            //Autocomplete Route
            RouteTable.Routes.Add("AutoComplete" + pd.PageLink.ID.ToString(),
                new Route(path + GetRoutemapFromXml("/wiki/urlroutes/autocomplete"), new WikiRouteHandler(pd.PageLink, "~/EPiServer.Research.Wiki/Pages/autocomplete.aspx", AccessLevel.Undefined)));
            //Search Route
            RouteTable.Routes.Add("SearchRoute" + pd.PageLink.ID.ToString(),
                new Route(path + GetRoutemapFromXml("/wiki/urlroutes/search", "{*Query}"), new WikiRouteHandler(pd.PageLink, "~/EPiServer.Research.Wiki/Pages/Search.aspx", AccessLevel.Read)));
            //New Article route
            RouteTable.Routes.Add("NewArticleRoute" + pd.PageLink.ID.ToString(),
                new Route(path + GetRoutemapFromXml("/wiki/urlroutes/newarticle", "{ArticleName}"), new WikiRouteHandler(pd.PageLink, "~/EPiServer.Research.Wiki/Pages/NewArticle.aspx", AccessLevel.Create)));

        }

        private static void RemoveRootRoutes(string WikiID)
        {
            RouteTable.Routes.Remove(RouteTable.Routes["NewArticleRoute" + WikiID]);
            RouteTable.Routes.Remove(RouteTable.Routes["CategoryRoute" + WikiID]);
            RouteTable.Routes.Remove(RouteTable.Routes["AutoComplete" + WikiID]);
            RouteTable.Routes.Remove(RouteTable.Routes["SearchRoute" + WikiID]);
        }

        private static string GetRoutemapFromXml(string key, params string[] elements)
        {
            if (elements != null) return string.Format(LanguageManager.Instance.Translate(key), elements);
            else return LanguageManager.Instance.Translate(key);
        }


        public string CreateCategoryListingUrl(PageReference cur, string Category1, string Category2)
        {
            return GetUrlRelativeToWikiRoot(cur, GetRoutemapFromXml("/wiki/urlroutes/category", Category1, Category2));
        }

        public string CreateSearchUrl(PageReference cur, string Query)
        {
            return GetUrlRelativeToWikiRoot(cur, GetRoutemapFromXml("/wiki/urlroutes/search", HttpUtility.UrlEncode(Query)));
        }

        public string CreateAutocompleteUrl(PageReference cur)
        {
            return GetUrlRelativeToWikiRoot(cur, GetRoutemapFromXml("/wiki/urlroutes/autocomplete"));
        }

        public string CreateNewArticleUrl(PageReference cur, string ArticleName)
        {
            return GetUrlRelativeToWikiRoot(cur, GetRoutemapFromXml("/wiki/urlroutes/newarticle", HttpUtility.UrlEncode(ArticleName)));
        }

        #endregion

        #region RightClick


        public void RemoveRightClick(PageBase wikiroot)
        {
            if (!wikiroot.CurrentPage.QueryDistinctAccess(AccessLevel.Administer))
            {
                wikiroot.ContextMenu.Menu.Items.Clear();
            }

        }

        #endregion

        #region ArticleManagement

        /// <summary>
        /// Add a new article to a wiki
        /// </summary>
        /// <param name="CurrentPage">Somewhere in the structure of the wiki to add the article to</param>
        /// <param name="Name">The name of the new page</param>
        /// <param name="Body">The body text of the new page</param>
        /// <param name="Comment">Version comment</param>
        /// <returns></returns>
        public PageReference CreateNewArticle(PageReference CurrentPage, string Name, string Body, string Comment, string UrlSegment, bool ShowTOC, int[] Categories)
        {
            //TODO: Consider validation
            PageReference parent = GetWikiRoot(CurrentPage).PageLink;
            PageData pd;

            if (LanguageBranch.ListEnabled().Count == 1)
            {
                pd = DataFactory.Instance.GetDefaultPageData(parent, WIKIARTICLENAME);
            }
            else
            {


                pd = DataFactory.Instance.GetDefaultPageData(parent, WIKIARTICLENAME, new LanguageSelector(LanguageBranch.Load(DataFactory.Instance.GetPage(CurrentPage).LanguageBranch).LanguageID));
                //Multi-languages
                //pd = DataFactory.Instance.GetDefaultPageData(parent, WIKIARTICLENAME, new LanguageSelector(LanguageBranch.Load(HttpContext.Current.Items["lang"].ToString()).LanguageID));
            }

            pd.SetValue("PageName", Name);
            pd.SetValue("MainBody", Body);
            if (string.IsNullOrEmpty(Comment))
            {
                Comment = "Page Created";//TODO: Hardcoded text
            }
            pd.SetValue("EditComment", Comment);

            pd.SetValue("ShowTOC", (ShowTOC) ? ((object)true) : null);
            pd.Category.Clear();
            Categories.ToList().ForEach(id => pd.Category.Add(id));

            if (!string.IsNullOrEmpty(UrlSegment))
            {
                UrlSegment = RemoveDiacritics(UrlSegment);

                pd.SetValue("PageURLSegment", UrlSegment);
            }
            return DataFactory.Instance.Save(pd, EPiServer.DataAccess.SaveAction.Publish);
        }

        /// <summary>
        /// Updates the article
        /// </summary>
        /// <param name="article"></param>
        /// <param name="Name"></param>
        /// <param name="Body"></param>
        /// <param name="Comment"></param>
        public void UpdateArticle(PageReference article, string Name, string Body, string Comment, bool ShowTOC, int[] Categories)
        {

            PageData pd = DataFactory.Instance.GetPage(article).CreateWritableClone();
            bool changed = false;
            if ((!string.IsNullOrEmpty(Name)) && (pd.PageName != Name))
            {
                pd.SetValue("PageName", Name);
                changed = true;
            }
            if (Body != (string)pd["MainBody"])
            {
                pd.SetValue("MainBody", Body);
                changed = true;
            }

            if (!((ShowTOC && pd["ShowTOC"] != null) || (!ShowTOC && pd["ShowTOC"] == null)))
            {
                changed = true;
                pd.SetValue("ShowTOC", (ShowTOC) ? ((object)true) : null);

            } if (pd.Category.Count != Categories.Length)
            {
                pd.Category.Clear();
                Categories.ToList().ForEach(id => pd.Category.Add(id));
                changed = true;
            }


            if (!string.IsNullOrEmpty(Comment))
            {
                changed = true;
            }
            pd.SetValue("EditComment", Comment);
            if (changed)
            {
                DataFactory.Instance.Save(pd, EPiServer.DataAccess.SaveAction.Publish);
            }

        }

        public PageReference[] FindArticlesByName(PageReference cur, Func<string, bool> predicate)
        {
            PageReference rt = GetWikiRoot(cur).PageLink;
            return WikiPageNames[rt].Keys.Where(predicate).Select(s => WikiPageNames[rt][s]).ToArray();
        }

        /// <summary>
        /// Returns the reference to a random wiki article
        /// </summary>
        /// <param name="cur">Current context</param>
        /// <returns></returns>
        public PageReference GetRandomArticle(PageReference cur)
        {
            Random r = new Random();
            PageReference root = GetWikiRoot(cur).PageLink;

            if (WikiPageNames.ContainsKey(root))
            {
                string[] keys = WikiPageNames[root].Keys.ToArray();
                return WikiPageNames[root][keys[r.Next(0, keys.Length)]];
            }
            else return cur;
        }

        public PageDataCollection GetAllArticles(PageReference root)
        {
            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();

            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WikiManager.WIKIARTICLENAME).ID.ToString(), false));
            PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(root, pcc);
            return pdc;
        }

        /// <summary>
        /// Returns a list of recently updated articles
        /// </summary>
        /// <param name="cur">Current Context</param>
        /// <returns></returns>
        public PageDataCollection RecentlyUpdatedArticles(PageReference cur)
        {
            PageData pd = GetWikiRoot(cur);

            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();

            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WikiManager.WIKIARTICLENAME).ID.ToString(), false));
            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WikiManager.WIKISECTIONNAME).ID.ToString(), false));
            PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(pd.PageLink, pcc);
            new FilterPropertySort("PageChanged", FilterSortDirection.Descending).Filter(pdc);

            return pdc;

        }

        /// <summary>
        /// Finds all articles in a given category
        /// </summary>
        /// <param name="cur"></param>
        /// <param name="CategoryName"></param>
        /// <returns></returns>
        public PageDataCollection FindArticlesInCategory(PageReference cur, params string[] CategoryNames)
        {
            PageData pd = GetWikiRoot(cur);
            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();
            var cats = CategoryNames.Where(cn => Category.Find(cn) != null).Select(cn => Category.Find(cn).ID.ToString()).ToArray();

            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageCategory", PropertyDataType.Category, string.Join(",", cats), true));

            PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(pd.PageLink, pcc);
            return new PageDataCollection(pdc.OrderBy(p => p.PageName).ToArray());
        }

        /// <summary>
        /// Returns a list of recently created articles
        /// </summary>
        /// <param name="cur"></param>
        /// <returns></returns>
        public PageDataCollection RecentlyCreatedArticles(PageReference cur)
        {
            PageData pd = GetWikiRoot(cur);

            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();

            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WikiManager.WIKIARTICLENAME).ID.ToString(), true));
            PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(pd.PageLink, pcc);
            new FilterPropertySort("PageCreated", FilterSortDirection.Descending).Filter(pdc);
            return pdc;
        }

        /// <summary>
        /// Returns the most discussed articles
        /// </summary>
        /// <param name="cur"></param>
        /// <param name="MaxCount"></param>
        /// <returns></returns>
        public PageDataCollection MostDiscussed(PageReference cur, int MaxCount)
        {
            PageData pd = GetWikiRoot(cur);

            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();

            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WikiManager.WIKIDISCUSSIONNAME).ID.ToString(), false));

            PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(pd.PageLink, pcc);
            //Select all Discussions
            var res = from p in pdc
                      group p by p.ParentLink into g
                      select new { Parent = g.Key, Count = g.Count() };
            res = res.OrderBy(a => a.Count).Reverse().Take(MaxCount);
            PageDataCollection rt = new PageDataCollection();
            //Lookup parents
            foreach (var a in res)
            {
                PageData r = DataFactory.Instance.GetPage(a.Parent).CreateWritableClone();
                r.Property.Add("DiscussionCount", new PropertyNumber(a.Count));
                rt.Add(r);
            }

            return rt;

        }


        #endregion

        #region SectionManagement

        /// <summary>
        /// Creates a new section
        /// </summary>
        /// <param name="Article"></param>
        /// <param name="Name"></param>
        /// <param name="Type"></param>
        /// <param name="Body"></param>
        /// <param name="Comment"></param>
        /// <returns></returns>
        public PageReference CreateNewSection(PageReference Article, string Name, string Type, string Body, string Comment, PageReference BelowThis)
        {
            PageReference parent = GetWikiArticle(Article).PageLink;
            PageData pd = DataFactory.Instance.GetDefaultPageData(parent, WIKISECTIONNAME);
            pd.SetValue("PageName", Name);
            pd.SetValue("MainBody", Body);
            pd.SetValue("PageCategory", new CategoryList());
            if (string.IsNullOrEmpty(Comment))
            {
                Comment = "Page Created";//TODO: Hardcoded text
            }
            pd.SetValue("EditComment", Comment);

            //Set PagePeerOrder
            PageDataCollection pdc = GetSections(parent);
            if (BelowThis == PageReference.EmptyReference)
            {
                //Add to the end
                pd["PagePeerOrder"] = (pdc.Count > 0) ? ((int)pdc[pdc.Count - 1]["PagePeerOrder"]) + SECTIONDIST : SECTIONDIST;
            }
            else
            {
                int before;
                int after;
                if (parent == BelowThis)
                {
                    //Add new section below parent
                    before = 0;
                    after = (pdc.Count > 0) ? (int)pdc[0]["PagePeerOrder"] : (SECTIONDIST * 2);
                }
                else
                {
                    before = (int)DataFactory.Instance.GetPage(BelowThis)["PagePeerOrder"];
                    if (BelowThis == pdc[pdc.Count - 1].PageLink)
                    {
                        //before is last section
                        after = before + (SECTIONDIST * 2);
                    }
                    else
                    {
                        after = (int)pdc[pdc.Find(BelowThis) + 1]["PagePeerOrder"];
                    }
                }
                pd["PagePeerOrder"] = before + ((after - before) / 2);
            }


            return DataFactory.Instance.Save(pd, EPiServer.DataAccess.SaveAction.Publish);
        }


        /// <summary>
        /// Updates a section after editing
        /// </summary>
        /// <param name="Section"></param>
        /// <param name="Name"></param>
        /// <param name="Type"></param>
        /// <param name="Body"></param>
        /// <param name="Comment"></param>
        public void UpdateSection(PageReference Section, string Name, string Type, string Body, string Comment)
        {
            PageData pd = DataFactory.Instance.GetPage(Section).CreateWritableClone();
            bool changed = false;
            if ((!string.IsNullOrEmpty(Name)) && (pd.PageName != Name))
            {
                pd.SetValue("PageName", Name);
                changed = true;
            }
            if ((!string.IsNullOrEmpty(Type)) && ((string)pd["Type"] != Type))
            {
                pd.SetValue("Style", Type);
                changed = true;
            }
            if (Body != (string)pd["MainBody"])
            {
                pd.SetValue("MainBody", Body);
                changed = true;
            }
            if (!string.IsNullOrEmpty(Comment))
            {
                changed = true;
            }
            pd.SetValue("EditComment", Comment);
            if (changed)
            {
                DataFactory.Instance.Save(pd, EPiServer.DataAccess.SaveAction.Publish);
            }

        }


        private void SwitchSections(PageData s1, PageData s2)
        {
            PageData p1 = s1.CreateWritableClone();
            PageData p2 = s2.CreateWritableClone();
            object o = p1["PagePeerOrder"];
            p1["PagePeerOrder"] = p2["PagePeerOrder"];
            p2["PagePeerOrder"] = o;
            p1["EditComment"] = "Section Moved";
            p2["EditComment"] = "Section Moved";
            DataFactory.Instance.Save(p1, EPiServer.DataAccess.SaveAction.Publish);
            DataFactory.Instance.Save(p2, EPiServer.DataAccess.SaveAction.Publish);
        }

        /// <summary>
        /// Returns a list of undeleted sections from the current article
        /// </summary>
        /// <param name="pr"></param>
        /// <returns></returns>
        public PageDataCollection GetSections(PageReference pr)
        {
            PageReference article = WikiManager.Instance.GetWikiArticle(pr).PageLink;
            PageDataCollection pdc = DataFactory.Instance.GetChildren(article);
            //Isolate undeleted sections
            var pages = from p in pdc
                        where p.PageTypeName == WIKISECTIONNAME && (bool)(p["Deleted"] ?? false) == false
                        select p;

            return new PageDataCollection(pages.ToArray());
        }

        public bool CanMoveUpSection(PageReference Section)
        {
            PageDataCollection siblings = GetSections(Section);
            int idx = siblings.Find(Section);
            return idx > 0;
        }

        public void MoveUpSection(PageReference Section)
        {
            PageDataCollection siblings = GetSections(Section);
            int idx = siblings.Find(Section);
            SwitchSections(siblings[idx - 1], siblings[idx]);
        }

        public void MoveDownSection(PageReference Section)
        {
            PageDataCollection siblings = GetSections(Section);
            int idx = siblings.Find(Section);
            SwitchSections(siblings[idx], siblings[idx + 1]);
        }

        public bool CanMoveDownSection(PageReference Section)
        {
            PageDataCollection siblings = GetSections(Section);
            int idx = siblings.Find(Section);
            return idx < (siblings.Count - 1);
        }

        /// <summary>
        /// Deletes a section
        /// </summary>
        /// <param name="Section"></param>
        public void DeleteSection(PageReference Section)
        {
            PageData pd = DataFactory.Instance.GetPage(Section).CreateWritableClone();
            pd["Deleted"] = true;
            pd["EditComment"] = "Deleted";
            DataFactory.Instance.Save(pd, EPiServer.DataAccess.SaveAction.Publish);
        }

        #endregion

        #region Discussion Management

        /// <summary>
        /// Retrieves the latest discussion posts, newest first
        /// </summary>
        /// <param name="cur"></param>
        /// <param name="Count"></param>
        public PageDataCollection GetDiscussions(PageReference cur, int MaxCount, int SkipCount)
        {

            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();

            pcc.Add(WikiManager.Instance.CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WikiManager.WIKIDISCUSSIONNAME).ID.ToString(), true));
            PageDataCollection pdc = DataFactory.Instance.FindPagesWithCriteria(cur, pcc);
            new FilterPropertySort("StartPublish", FilterSortDirection.Ascending).Filter(pdc);
            pdc.Skip(SkipCount);
            pdc.Take(MaxCount);
            return pdc;


            /* var res = from p in DataFactory.Instance.GetChildren(this.GetWikiArticle(cur).PageLink)
                       where p.PageTypeName == WIKIDISCUSSIONNAME
                       orderby p.StartPublish descending
                       select p;
           
             return new PageDataCollection(res.Skip(SkipCount).Take(MaxCount).ToArray());
             * */
        }


        /// <summary>
        /// Adds a new dicussion post
        /// </summary>
        /// <param name="cur"></param>
        /// <param name="Text"></param>
        public void AddDicussionPost(PageReference cur, string Text)
        {
            PageData pd = DataFactory.Instance.GetDefaultPageData(GetWikiArticle(cur).PageLink, WIKIDISCUSSIONNAME);
            pd.SetValue("PageName", DateTime.Now.ToString());
            pd.SetValue("MainBody", Text);
            pd.SetValue("PageCategory", new CategoryList());
            DataFactory.Instance.Save(pd, EPiServer.DataAccess.SaveAction.Publish);
        }

        #endregion

        #region Search Management





        public string GetPreviewText(object dataItem, PageReference cur)
        {
            PageData page = dataItem as PageData;

            if (page == null)
            {
                return string.Empty;
            }


            string previewText = page["MainBody"] as string;
            if (String.IsNullOrEmpty(previewText))
            {
                return string.Empty;
            }


            return TextIndexer.StripHtml(Parser.Parse(previewText, cur), _previewTextLength);
        }


        #endregion

        #region Utilities

        /// <summary>
        /// Gets the friendly URL of a page
        /// </summary>
        /// <param name="PageLink"></param>
        /// <param name="URL"></param>
        /// <returns></returns>
        public static string GetFriendlyURL(PageReference PageLink, string URL)
        {
            UrlBuilder url = new UrlBuilder(URL);

            EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, PageLink, System.Text.UTF8Encoding.UTF8);
            return url.ToString().Replace(" ", "+");


        }

        public static String RemoveDiacritics(string s)
        {
            string normalizedString = s.Normalize(NormalizationForm.FormD);
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < normalizedString.Length; i++)
            {
                char c = normalizedString[i];
                if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
                    stringBuilder.Append(c);
            }
            return stringBuilder.ToString();
        }

        public static string ConstructAppRelativePath(PageReference PageLink)
        {
            if (PageLink == PageReference.StartPage) return ""; //TODO: Lang code?
            PageData pd = DataFactory.Instance.GetPage(PageLink);
            return ConstructAppRelativePath(pd.ParentLink) + "/" + pd["PageURLSegment"].ToString();
        }


        public string GetWikiRootFriendlyUrl(PageReference cur)
        {
            PageData pd = GetWikiRoot(cur);
            return GetFriendlyURL(pd.PageLink, pd.LinkURL);
        }

        public string GetUrlRelativeToWikiRoot(PageReference cur, string RelativeUrl)
        {
            return GetWikiRootFriendlyUrl(cur) + RelativeUrl.TrimStart('/');
        }

        #endregion



        public WikiManager()
        {
            Parser = new WikiParser();

        }


        #region Article History

        public PageVersionCollection ListAllArticleVersions(PageReference article)
        {

            PageVersionCollection final = PageVersion.List(article);



            foreach (PageData pd in GetSections(article, true))
            {
                final.AddRange(PageVersion.List(pd.PageLink));
            }

            final.Sort(WikiManager.CompareVersionDates);

            return final;

        }

        public static int CompareVersionDates(PageVersion x, PageVersion y)
        {

            if (x == null || y == null || x.Saved == y.Saved)
                return 0;


            return x.Saved < y.Saved ? 1 : -1;
        }

        public PageDataCollection GetSections(PageReference pr, bool includeDeleted)
        {
            PropertyCriteriaCollection pcc = new PropertyCriteriaCollection();
            pcc.Add(CreateCriteria(CompareCondition.Equal, "PageTypeID", PropertyDataType.PageType, PageType.Load(WIKISECTIONNAME).ID.ToString(), true));
            return DataFactory.Instance.FindPagesWithCriteria(pr, pcc);

        }



        public void RestoreEntireArticle(PageReference SectionOrArticle, DateTime Versiontime)
        {
            PageReference articlepage = GetWikiArticle(SectionOrArticle).PageLink;
            PageVersion Article = GetVersion(articlepage, Versiontime, true);
            if (null == Article)
                return;
            //Save the article again
            PageData newArticle = DataFactory.Instance.GetPage(Article.ID).CreateWritableClone();
            newArticle["EditComment"] = "Changed to version from date: " + Versiontime;
            DataFactory.Instance.Save(newArticle, EPiServer.DataAccess.SaveAction.Publish);

            foreach (PageData pd in GetSections(articlepage, true))
            {
                PageVersion pv = GetVersion(pd.PageLink, Versiontime, false);
                PageData newsection;
                if (pv == null)
                {
                    //TODO :Get the correct pagedata version and set that ones property deleted to true not just the most recent one
                    newsection = pd.CreateWritableClone();
                    newsection["Deleted"] = true;
                    newsection["EditComment"] = "Not visible in version from date: " + Versiontime;

                }
                else
                {
                    newsection = DataFactory.Instance.GetPage(pv.ID).CreateWritableClone();
                    newsection["EditComment"] = "Changed to version from date: " + Versiontime;
                }

                DataFactory.Instance.Save(newsection, EPiServer.DataAccess.SaveAction.Publish, AccessLevel.NoAccess);


            }
        }

        public void RestoreToVersion(PageReference SectionOrArticle, DateTime Versiontime)
        {
            PageVersion pv = GetVersion(SectionOrArticle, Versiontime, true);
            PageData newArticle = DataFactory.Instance.GetPage(pv.ID).CreateWritableClone();
            newArticle["EditComment"] = "Changed to version from date: " + Versiontime;
            DataFactory.Instance.Save(newArticle, EPiServer.DataAccess.SaveAction.Publish);

        }



        public PageVersion GetVersion(PageReference pf, DateTime VersionTime, bool isArticle)
        {
            PageVersionCollection pvc = PageVersion.List(pf);
            PageVersion pv;

            for (int i = 0; i < pvc.Count; i++)
            {
                pv = pvc[i];

                if (pv.Saved > VersionTime)
                {

                    if (i == 0 && !isArticle)
                    {
                        return null;
                    }
                    pv = pvc[i - 1];
                    return pv;


                }

            }
            return pvc[pvc.Count - 1];




        }


        #endregion
    }

    public class PageDataSorter : IComparer<PageData>
    {
        public PageDataSorter()
        {

        }

        int IComparer<PageData>.Compare(PageData x, PageData y)
        {
            if (x == null || y == null)
                return 0;



            PageDataCollection Xpdc = WikiManager.Instance.GetDiscussions(x.PageLink, 100, 0);
            PageDataCollection Ypdc = WikiManager.Instance.GetDiscussions(y.PageLink, 100, 0);

            if (Xpdc.Count == Ypdc.Count)
                return 0;



            return Xpdc.Count < Ypdc.Count ? 1 : -1;
        }

    }




}
